﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using BaseTypesLib.Model;
using System.Windows.Input;
using System.Windows.Documents;
using BBInterfaceNET.Designer.Views;
using System.Diagnostics;
using System.Windows.Interactivity;
using System.Reflection;
using BBInterfaceNET.Designer.ViewModels;
using ExplorerInfrastructure;

namespace BBInterfaceNET.Designer.Behaviors
{
    public class DragMoveBehavior : Behavior<FrameworkElement>
    {
        FrameworkElement obj;//the associated object;
        FrameworkElement draggingObject;//the object that is being dragged;
        FrameworkElement currentManager;//the first manager below the mouse;
        private bool dragging;
        private bool prepareToDrag;
        private Point origin;
        private ControlAdorner adorner;
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.AllowDrop = true;
            AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
        }

        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            obj = AssociatedObject;
            obj.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            obj.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
            obj.MouseMove += AssociatedObject_MouseMove;
            obj.Drop += AssociatedObject_Drop;
        }
        protected override void OnDetaching()
        {
            base.OnDetaching();

            obj.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
            obj.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
            obj.MouseMove -= AssociatedObject_MouseMove;
            obj.Drop -= AssociatedObject_Drop;
        }

        void AssociatedObject_Drop(object sender, DragEventArgs e)
        {
            Field newObj = null;
            if (e.Data.GetDataPresent(typeof(string)))
            {
                string present = e.Data.GetData(typeof(string)) as string;
                Type type = Type.GetType(present);
             //   Type type = Assembly.Load("BaseTypesLib").GetType(present);
                if (type == null) return;
                ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
                newObj = ci.Invoke(null) as Field;
            }

            if (newObj == null) return;

            Point actual = e.GetPosition(obj);
            currentManager = GetFirstHitManager(actual);
            if (currentManager == null) return;
            currentManager.CaptureMouse();

            FrameworkElement draggingObject = new FrameworkElement();
            draggingObject.DataContext = newObj;

            Field source = draggingObject.DataContext as Field;
            Manager target = currentManager.DataContext as Manager;

            if (source != null && target != null)
            {
                int idx = -1;
                //get the position of the new object
                idx = GetInsertPosition(currentManager, draggingObject);
                //inset the object at the corresponding position
                if (idx != -1)
                {
                    if (source.Manager != null)
                        source.Manager.RemoveField(source);
                    if (idx >= target.Fields.Count)
                        target.AddField(source);
                    else
                        target.InsertField(idx, source);
                    FileDesignerViewModel vm = GetViewModel(e.Source);
                    if (vm != null)
                    {
                        vm.FieldDropped(source);
                    }
                }

            }

            currentManager.ReleaseMouseCapture();
        }
        private FileDesignerViewModel GetViewModel(object source)
        {
            FrameworkElement fr = source as FrameworkElement;
            if (fr != null)
            {
                FileDesignerViewModel vm = fr.DataContext as FileDesignerViewModel;
                if (vm != null)
                    return vm;
                return null;
            }
            return null;
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!dragging && !prepareToDrag)
            {
                origin = e.GetPosition(obj);

                HitTestResult res = VisualTreeHelper.HitTest(obj, origin);
                if (res != null)
                {
                    draggingObject = res.VisualHit as FrameworkElement;
                    if (draggingObject == null) return;
                    //need to find the first element that has a field as its data context
                    //these elements will be the ones that will be dragged
                    Field dc = draggingObject.DataContext as Field;
                    while (dc==null)
                    {
                        draggingObject = VisualTreeHelper.GetParent(draggingObject) as FrameworkElement;
                        if (draggingObject == null) break;
                        dc = draggingObject.DataContext as Field;
                    }
                    if (draggingObject == null || dc==null) return;
                    //the data context should be a field at this point
                    //the field needs to be inside a manager in order for the dragging
                    //to start

                    if (dc.Manager == null) return;

                    //get the upper most visual that has this data context
                    while (draggingObject.DataContext == dc)
                    {
                        FrameworkElement el = VisualTreeHelper.GetParent(draggingObject) as FrameworkElement;
                        if (el != null && el.DataContext == dc)
                        {
                            draggingObject = el;
                        }
                        else
                            break;
                    }

                    obj.CaptureMouse();

                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(draggingObject);
                    adorner = new ControlAdorner(draggingObject);
                    adorner.IsHitTestVisible = false;
                    adornerLayer.Add(adorner);

                    prepareToDrag = true;
                }
            }
         //   e.Handled = true;
        }
        
        void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (dragging || prepareToDrag)
            {
                dragging = true;
                prepareToDrag = false;
                if(draggingObject.Opacity!=0)
                    draggingObject.Opacity = 0;
                Point actual = e.GetPosition(obj);
                //update the placeholder 
                UpdatePlaceholder(actual);
                //update the insertion point
                currentManager = GetFirstHitManager(actual);
                //UpdateInsertion(actual);
                //Debug.WriteLine(string.Format("intersection: {0}, placeholder: {1}", adorner.InsertionRect, adorner.PlaceholderRect));
                adorner.InvalidateVisual();
                origin = actual;
            }
        //    e.Handled = true;
        }
        
        private void RemoveAdorner()
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(draggingObject);
            Adorner[] adorners = adornerLayer.GetAdorners(draggingObject);
            foreach (Adorner a in adorners)
            {
                if (a is ControlAdorner)
                    adornerLayer.Remove(a);
            }
        }
        
        void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (prepareToDrag)
            {
                prepareToDrag = false;
                RemoveAdorner();
                obj.ReleaseMouseCapture();
                draggingObject = null;
            }
            
            if (dragging)
            {
                obj.ReleaseMouseCapture();
                draggingObject.Opacity = 1;

                RemoveAdorner();

                dragging = false;
                if (currentManager != null)
                {
                    Field source = draggingObject.DataContext as Field;
                    Manager target = currentManager.DataContext as Manager;
                    if (source != null && target != null)
                    {
                        int idx = -1;
                        //get the position of the new object
                        idx = GetInsertPosition(currentManager, draggingObject);
                        //inset the object at the corresponding position
                        if (idx != -1)
                        {
                            if (source.Manager != null)
                                source.Manager.RemoveField(source);
                            if (idx >= target.Fields.Count)
                                target.AddField(source);
                            else
                                target.InsertField(idx, source);
                            FileDesignerViewModel vm = GetViewModel(e.Source);
                            if (vm != null)
                                vm.IsDirty = true;
                        }

                    }
                }

                draggingObject = null;
            }
         //   e.Handled = true;
        }

        private int GetInsertPosition(FrameworkElement currentManager,
            FrameworkElement draggingObject)
        {
            //also get the original manager for the dragging object
            
            ItemsControl parent = currentManager as ItemsControl;
            Point mgrPos = Mouse.GetPosition(currentManager);
            Manager mgr = currentManager.DataContext as Manager;
            bool vert = mgr != null && mgr.ManagerLayout == ManagerLayout.Vertical;
            bool hor = mgr != null && mgr.ManagerLayout == ManagerLayout.Horizontal;
            int res = 0;
            if (vert)
            {
                //get the element that is under the mouse
                FrameworkElement el = GetVertElementUnderneath(parent, mgrPos);
                if (el == null)
                {
                    return parent.Items.Count;
                }
                //get the container for the object that is dragged
                DependencyObject origContainer = parent.ItemContainerGenerator.ContainerFromItem(draggingObject.DataContext);
                int origIdx = -1;
                if (origContainer != null)
                    origIdx = parent.ItemContainerGenerator.IndexFromContainer(origContainer);
                //get the items control that contains el
                int currIdx = parent.ItemContainerGenerator.IndexFromContainer(parent.ItemContainerGenerator.ContainerFromItem(el.DataContext));

                if (origContainer == null)
                {   //the object came from a different container
                    //only the current index matters here since the elements contained by the container
                    //i'm dragging the new element into don't change. the dragger element is completly new
                    double ah = el.ActualHeight / 2;
                    if (Mouse.GetPosition(el).Y < ah)
                    {
                        res = currIdx;
                    }
                    else
                    {
                        res = currIdx + 1;
                    }
                }
                else
                {//dragging in the same container
                    bool val1 = (currIdx + 1) == origIdx;
                    bool val2 = (currIdx - 1) == origIdx;

                    double ah1 = el.ActualHeight / 2;
                    val1 = val1 && Mouse.GetPosition(el).Y > ah1;

                    double ah2 = el.ActualHeight / 2;
                    val2 = val2 && Mouse.GetPosition(el).Y < ah2;

                    if (!(origIdx == currIdx || val1 || val2))
                    {
                        double ah = el.ActualHeight / 2;
                        if (Mouse.GetPosition(el).Y < ah)
                        {
                            if (origIdx < currIdx)
                            {//Top->down
                                res = currIdx - 1;
                            }
                            else
                            {
                                res = currIdx;
                            }
                        }
                        else
                        {
                            if (origIdx < currIdx)
                            {//top->down
                                res = currIdx ;
                            }
                            else
                            {
                                res = currIdx + 1;
                            }
                        }
                    }
                    else { res = origIdx; }
                }

            }
            else if (hor)
            {
                //get the element that is under the mouse
                FrameworkElement el = GetHorizElementUnderneath(parent, mgrPos);
                if (el == null)
                {
                    return parent.Items.Count;
                }
                //get the container for the object that is dragged
                DependencyObject origContainer = parent.ItemContainerGenerator.ContainerFromItem(draggingObject.DataContext);
                int origIdx = -1;
                if (origContainer != null)
                    origIdx = parent.ItemContainerGenerator.IndexFromContainer(origContainer);
                //get the items control that contains el
                int currIdx = parent.ItemContainerGenerator.IndexFromContainer(parent.ItemContainerGenerator.ContainerFromItem(el.DataContext));
                if (origContainer == null)
                {
                    //the object came from a different container
                    //only the current index matters here since the elements contained by the container
                    //i'm dragging the new element into don't change. the dragger element is completly new
                    double aw = el.ActualWidth / 2;
                    if (Mouse.GetPosition(el).X < aw)
                    {
                        res = currIdx;
                    }
                    else
                    {
                        res = currIdx + 1;
                    }
                }
                else
                {//dragging in the same container
                    bool val1 = (currIdx + 1) == origIdx;
                    bool val2 = (currIdx - 1) == origIdx;

                    double aw1 = el.ActualWidth / 2;
                    val1 = val1 && Mouse.GetPosition(el).X > aw1;

                    double aw2 = el.ActualWidth / 2;
                    val2 = val2 && Mouse.GetPosition(el).X < aw2;

                    if (!(origIdx == currIdx || val1 || val2))
                    {
                        double aw = el.ActualWidth / 2;
                        if (Mouse.GetPosition(el).X < aw)
                        {
                            if (origIdx < currIdx)
                            {//Top->down
                                res = currIdx - 1;
                            }
                            else
                            {
                                res = currIdx;
                            }
                        }
                        else
                        {
                            if (origIdx < currIdx)
                            {//top->down
                                res = currIdx;
                            }
                            else
                            {
                                res = currIdx + 1;
                            }
                        }
                    }
                    else { res = origIdx; }
                }
            }
            else
            {
                res = parent.Items.Count;
            }
            return res;
        }

        private void UpdatePlaceholder(Point actual)
        {
            double dx = actual.X - origin.X;
            double dy = actual.Y - origin.Y;
            
            Rect rect = adorner.PlaceholderRect;
            rect.X = rect.X + dx;
            rect.Y = rect.Y + dy;

            if (rect.Width == 0 || rect.Height == 0)
            {
                FrameworkElement fe = null;
                if (VisualTreeHelper.GetChildrenCount(draggingObject) > 0)
                    fe = VisualTreeHelper.GetChild(draggingObject, 0) as FrameworkElement;

                if (rect.Width == 0)
                {
                    if (fe != null) rect.Width = fe.ActualWidth;
                    else rect.Width = draggingObject.ActualWidth;
                }

                if (rect.Height == 0)
                {
                    if (fe != null) rect.Height = fe.ActualHeight;
                    else rect.Height = draggingObject.ActualHeight;
                }
            }

            adorner.PlaceholderRect = rect;
        }


        private FrameworkElement GetVertElementUnderneath(ItemsControl parent, Point actual)
        {
            int idx = -1;
            double h = 0;
            for (int i = 0; i < parent.Items.Count; i++)
            {
                FrameworkElement elem =
                parent.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
                if (elem == null) continue;
                h += elem.ActualHeight;
                if (h > actual.Y)
                {
                    idx = i;
                    break;
                }
            }
            if (idx == -1) return null;
            FrameworkElement el = parent.ItemContainerGenerator.ContainerFromIndex(idx) as FrameworkElement;
            return el;
        }
        private FrameworkElement GetHorizElementUnderneath(ItemsControl parent, Point actual)
        {
            int idx = -1;
            double w = 0;
            for (int i = 0; i < parent.Items.Count; i++)
            {
                FrameworkElement elem =
                parent.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
                if (elem == null) continue;
                w += elem.ActualWidth;
                if (w > actual.X)
                {
                    idx = i;
                    break;
                }
            }
            if (idx == -1) return null;
            FrameworkElement el = parent.ItemContainerGenerator.ContainerFromIndex(idx) as FrameworkElement;
            return el;
        }
        private FrameworkElement GetFirstHitManager(Point actual)
        {
            //get all managers that are under the click
            List<FrameworkElement> elems = new List<FrameworkElement>();
            VisualTreeHelper.HitTest(obj, null, (a) =>
            {
                FrameworkElement el = a.VisualHit as FrameworkElement;//).TemplatedParent as FrameworkElement;
                if (el!=null && !elems.Contains(el) && el.DataContext is Manager)
                    elems.Add(el);

                return HitTestResultBehavior.Continue;
            }, new PointHitTestParameters(actual));
            ItemsControl ic = null;
            //get the first manager under the mouse also watch out not to drop an 
            //object over itself
            FrameworkElement res = elems.FirstOrDefault(p =>
            {
                ic=null;
                FrameworkElement currentEl = p;
                object dc = p.DataContext;
                while (currentEl.DataContext == dc)
                {
                    FrameworkElement el = VisualTreeHelper.GetParent(currentEl) as FrameworkElement;
                    if (el != null && el.DataContext == dc)
                    {
                        currentEl = el;
                        if (el is ItemsControl) { ic = el as ItemsControl; }
                    }
                    else
                        break;
                }
                //select only if current is different from the dragging element
                //(don't drop a manager over itself)
                bool res2 = (currentEl != draggingObject);

                return res2;
            });
            
            return ic;
        }

    }
}
